<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>

                    <h4>调试</h4>
                    <div class="x-wiki-info"><span>860次阅读</span></div>
                    <hr style="border-top-color:#ccc" />
                    <div class="x-wiki-content x-content"><p>程序能一次写完并正常运行的概率很小，基本不超过1%。总会有各种各样的bug需要修正。有的bug很简单，看看错误信息就知道，有的bug很复杂，我们需要知道出错时，哪些变量的值是正确的，哪些变量的值是错误的，因此，需要一整套调试程序的手段来修复bug。</p>
<p>第一种方法简单直接粗暴有效，就是用<code>print</code>把可能有问题的变量打印出来看看：</p>
<pre><code># err.py
def foo(s):
    n = int(s)
    print &#39;&gt;&gt;&gt; n = %d&#39; % n
    return 10 / n

def main():
    foo(&#39;0&#39;)

main()
</code></pre><p>执行后在输出中查找打印的变量值：</p>
<pre><code>$ python err.py
&gt;&gt;&gt; n = 0
Traceback (most recent call last):
  ...
ZeroDivisionError: integer division or modulo by zero
</code></pre><p>用<code>print</code>最大的坏处是将来还得删掉它，想想程序里到处都是<code>print</code>，运行结果也会包含很多垃圾信息。所以，我们又有第二种方法。</p>
<h3 id="-">断言</h3>
<p>凡是用<code>print</code>来辅助查看的地方，都可以用断言（assert）来替代：</p>
<pre><code># err.py
def foo(s):
    n = int(s)
    assert n != 0, &#39;n is zero!&#39;
    return 10 / n

def main():
    foo(&#39;0&#39;)
</code></pre><p><code>assert</code>的意思是，表达式<code>n != 0</code>应该是<code>True</code>，否则，后面的代码就会出错。</p>
<p>如果断言失败，<code>assert</code>语句本身就会抛出<code>AssertionError</code>：</p>
<pre><code>$ python err.py
Traceback (most recent call last):
  ...
AssertionError: n is zero!
</code></pre><p>程序中如果到处充斥着<code>assert</code>，和<code>print</code>相比也好不到哪去。不过，启动Python解释器时可以用<code>-O</code>参数来关闭<code>assert</code>：</p>
<pre><code>$ python -O err.py
Traceback (most recent call last):
  ...
ZeroDivisionError: integer division or modulo by zero
</code></pre><p>关闭后，你可以把所有的<code>assert</code>语句当成<code>pass</code>来看。</p>
<h3 id="logging">logging</h3>
<p>把<code>print</code>替换为<code>logging</code>是第3种方式，和<code>assert</code>比，<code>logging</code>不会抛出错误，而且可以输出到文件：</p>
<pre><code># err.py
import logging

s = &#39;0&#39;
n = int(s)
logging.info(&#39;n = %d&#39; % n)
print 10 / n
</code></pre><p><code>logging.info()</code>就可以输出一段文本。运行，发现除了<code>ZeroDivisionError</code>，没有任何信息。怎么回事？</p>
<p>别急，在<code>import logging</code>之后添加一行配置再试试：</p>
<pre><code>import logging
logging.basicConfig(level=logging.INFO)
</code></pre><p>看到输出了：</p>
<pre><code>$ python err.py
INFO:root:n = 0
Traceback (most recent call last):
  File &quot;err.py&quot;, line 8, in &lt;module&gt;
    print 10 / n
ZeroDivisionError: integer division or modulo by zero
</code></pre><p>这就是<code>logging</code>的好处，它允许你指定记录信息的级别，有<code>debug</code>，<code>info</code>，<code>warning</code>，<code>error</code>等几个级别，当我们指定<code>level=INFO</code>时，<code>logging.debug</code>就不起作用了。同理，指定<code>level=WARNING</code>后，<code>debug</code>和<code>info</code>就不起作用了。这样一来，你可以放心地输出不同级别的信息，也不用删除，最后统一控制输出哪个级别的信息。</p>
<p><code>logging</code>的另一个好处是通过简单的配置，一条语句可以同时输出到不同的地方，比如console和文件。</p>
<h3 id="pdb">pdb</h3>
<p>第4种方式是启动Python的调试器pdb，让程序以单步方式运行，可以随时查看运行状态。我们先准备好程序：</p>
<pre><code># err.py
s = &#39;0&#39;
n = int(s)
print 10 / n
</code></pre><p>然后启动：</p>
<pre><code>$ python -m pdb err.py
&gt; /Users/michael/Github/sicp/err.py(2)&lt;module&gt;()
-&gt; s = &#39;0&#39;
</code></pre><p>以参数<code>-m pdb</code>启动后，pdb定位到下一步要执行的代码<code>-&gt; s = &#39;0&#39;</code>。输入命令<code>l</code>来查看代码：</p>
<pre><code>(Pdb) l
  1     # err.py
  2  -&gt; s = &#39;0&#39;
  3     n = int(s)
  4     print 10 / n
[EOF]
</code></pre><p>输入命令<code>n</code>可以单步执行代码：</p>
<pre><code>(Pdb) n
&gt; /Users/michael/Github/sicp/err.py(3)&lt;module&gt;()
-&gt; n = int(s)
(Pdb) n
&gt; /Users/michael/Github/sicp/err.py(4)&lt;module&gt;()
-&gt; print 10 / n
</code></pre><p>任何时候都可以输入命令<code>p 变量名</code>来查看变量：</p>
<pre><code>(Pdb) p s
&#39;0&#39;
(Pdb) p n
0
</code></pre><p>输入命令<code>q</code>结束调试，退出程序：</p>
<pre><code>(Pdb) n
ZeroDivisionError: &#39;integer division or modulo by zero&#39;
&gt; /Users/michael/Github/sicp/err.py(4)&lt;module&gt;()
-&gt; print 10 / n
(Pdb) q
</code></pre><p>这种通过pdb在命令行调试的方法理论上是万能的，但实在是太麻烦了，如果有一千行代码，要运行到第999行得敲多少命令啊。还好，我们还有另一种调试方法。</p>
<h3 id="pdb-set-_trace-">pdb.set_trace()</h3>
<p>这个方法也是用pdb，但是不需要单步执行，我们只需要<code>import pdb</code>，然后，在可能出错的地方放一个<code>pdb.set_trace()</code>，就可以设置一个断点：</p>
<pre><code># err.py
import pdb

s = &#39;0&#39;
n = int(s)
pdb.set_trace() # 运行到这里会自动暂停
print 10 / n
</code></pre><p>运行代码，程序会自动在<code>pdb.set_trace()</code>暂停并进入pdb调试环境，可以用命令<code>p</code>查看变量，或者用命令<code>c</code>继续运行：</p>
<pre><code>$ python err.py 
&gt; /Users/michael/Github/sicp/err.py(7)&lt;module&gt;()
-&gt; print 10 / n
(Pdb) p n
0
(Pdb) c
Traceback (most recent call last):
  File &quot;err.py&quot;, line 7, in &lt;module&gt;
    print 10 / n
ZeroDivisionError: integer division or modulo by zero
</code></pre><p>这个方式比直接启动pdb单步调试效率要高很多，但也高不到哪去。</p>
<h3 id="ide">IDE</h3>
<p>如果要比较爽地设置断点、单步执行，就需要一个支持调试功能的IDE。目前比较好的Python IDE有PyCharm：</p>
<p><a href="http://www.jetbrains.com/pycharm/">http://www.jetbrains.com/pycharm/</a></p>
<p>另外，<a href="http://eclipse.org/">Eclipse</a>加上<a href="http://pydev.org/">pydev</a>插件也可以调试Python程序。</p>
<h3 id="-">小结</h3>
<p>写程序最痛苦的事情莫过于调试，程序往往会以你意想不到的流程来运行，你期待执行的语句其实根本没有执行，这时候，就需要调试了。</p>
<p>虽然用IDE调试起来比较方便，但是最后你会发现，logging才是终极武器。</p>
</div>

                    <hr style="border-top-color:#ccc" />

                    